package com.g0dkar.leet.core.rest.actions;

import java.io.Serializable;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import br.com.caelum.vraptor.Get;
import br.com.caelum.vraptor.Path;
import br.com.caelum.vraptor.Resource;
import br.com.caelum.vraptor.Result;

import com.g0dkar.leet.core.Leet;
import com.g0dkar.leet.core.model.ClassMapping;
import com.g0dkar.leet.core.rest.responseObjects.RestResponse;

/**
 * This class does all the CRUD stuff with a REST API.
 * 
 * @author g0dkar
 * 
 */
@Resource
@Path("/api")
public class CrudController extends AbstractRestAction {
	
	public CrudController(Leet application, Result result, HttpServletRequest request) {
		super(application, result, request);
	}
	
	/**
	 * <h3>/api/{mappingName}/create</h3>
	 * <p>
	 * Creates a new entity on the database, and returns the newly created entity.
	 * </p>
	 * 
	 * <h4>HTTP Method</h4>
	 * <p>
	 * POST
	 * </p>
	 * 
	 * <h4>Return</h4>
	 * <p>
	 * The newly created object. If the return type is HTML, then the user will be redirected to the details page for
	 * the newly created object: /api/{mappingName}/view.html?id=[object.id]
	 * </p>
	 * 
	 * <h4>Examples</h4>
	 * <ol>
	 * <li>http://www.website.com/api/Example/create.json?name=Test&value=123</li>
	 * <li>http://www.website.com/api/Example-ex/create.json?ex.name=Test&ex.value=123</li>
	 * </ol>
	 * 
	 * @param _mappingName
	 *            {@link ClassMapping} name for the entity
	 * @param _format
	 *            The result format ("json", "xml" or "html")
	 * @param _prefix
	 *            Optional. The {@link ClassMapping} fields' prefix
	 * @return The newly created Object, or <code>null</code> if something went wrong.
	 */
	// Commented for testing purposes. It's easier to test by writing URLs :)
	// @Post
	@Path({ "{_mappingName:[a-zA-Z_][a-zA-Z0-9_]*}/create.{_format}", "{_mappingName:[a-zA-Z_][a-zA-Z0-9_]+}-{_prefix:[a-zA-Z_][a-zA-Z0-9_]*}/create.{_format}" })
	public Object create(String _mappingName, String _format, String _prefix) {
		Object newObject = null;
		
		if (application != null && application.hasClassMapping(_mappingName)) {
			ClassMapping classMapping = application.getClassMapping(_mappingName);
			Map<String, Object> values = buildEntityValuesMap(_prefix);
			
			newObject = classMapping.newInstance(values);
			
			// TODO Validate the newly created Object
			
			try {
				application.getPersistenceManager().save((Serializable) newObject);
			} catch (Exception e) {
				newObject = null;
				restReturn(result, _format, new RestResponse.Crud.Create(RestResponse.STATUS_EXCEPTION, e));
				return newObject;
			}
			
			restReturn(result, _format, new RestResponse.Crud.Create(RestResponse.STATUS_OK, newObject));
			return newObject;
		}
		
		restReturn(result, _format, new RestResponse.Crud.Create(RestResponse.STATUS_UNKNOWN_ERROR, null));
		return newObject;
	}
	
	@Get
	@Path("{mappingName}")
	public Object classMappingInformation(String mappingName) {
		return null;
	}
	
	/**
	 * <h3>/api/{mappingName}/list</h3>
	 * <p>
	 * Retrieves entities from the database.
	 * </p>
	 * 
	 * <h4>Parameters</h4>
	 * <table cellspacing="1" cellpadding="3" border="1">
	 * <tr><th>Parameter</th><th>Type</th><th>Accepted Values</th><th>Default</th><th>Example</th><th>Description</th></tr>
	 * <tr><td>_id</td><td>{@link Long}</td><td>[0-9]+</td><td>-</td><td>5</td><td>A single entity ID. This parameter abstracts the real ID field of the object.</td></tr>
	 * <tr><td>_limit</td><td>{@link String}</td><td>[0-9]+(,[0-9]+)?</td><td>25</td><td>5,10</td><td>The cursor starting position and how many objects will be returned. If this field is a single numeric value, then that many entities will be returned.</td></tr>
	 * <tr><td>_order</td><td>{@link String}</td><td>(?i)(asc|desc)</td><td>ASC</td><td>ASC or DESC</td><td>In which order the result will be ordered.</td></tr>
	 * </table>
	 * </p>
	 * 
	 * <h4>HTTP Method</h4>
	 * <p>
	 * GET
	 * </p>
	 * 
	 * <h4>Return</h4>
	 * <p>
	 * A single database entity if an ID is especified, or a list of entities (built from a database projection).
	 * </p>
	 * 
	 * <h4>Examples</h4>
	 * <ol>
	 * <li>http://www.website.com/api/Example/view.json?_id=5</li>
	 * <li>http://www.website.com/api/Example/view.xml?name=Example&_limit=10</li>
	 * <li>http://www.website.com/api/Example/view.xml (returns every entity on the database)</li>
	 * </ol>
	 * 
	 * @param _mappingName
	 *            {@link ClassMapping} name for the entity
	 * @param _format
	 *            The result format ("json", "xml" or "html")
	 * @param _prefix
	 *            Optional. The {@link ClassMapping} fields' prefix
	 * @return The newly created Object, or <code>null</code> if something went wrong.
	 */
	@Get
	@Path("{_mappingName:[a-zA-Z_][a-zA-Z0-9_]*}/list.{_format}")
	public Object list(String _mappingName, String _format, Long _id, String _limit) {
		Object object = null;
		
		if (application != null && application.hasClassMapping(_mappingName)) {
			ClassMapping classMapping = application.getClassMapping(_mappingName);
			
			if (_id == null || _id <= 0) {
				try {
					object = application.getPersistenceManager().findAll(classMapping.getType());
				} catch (Exception e) {
					e.printStackTrace();
					
					restReturn(result, _format, new RestResponse.Crud.View(RestResponse.STATUS_EXCEPTION, e));
					return e;
				}
			}
			else {
				try {
					object = application.getPersistenceManager().findById(_id, classMapping.getType());
				} catch (Exception e) {
					e.printStackTrace();
					
					restReturn(result, _format, new RestResponse.Crud.View(RestResponse.STATUS_EXCEPTION, e));
					return e;
				}
			}
			
			if (object != null) {
				restReturn(result, _format, new RestResponse.Crud.View(RestResponse.STATUS_OK, object));
				return object;
			}
			else {
				restReturn(result, _format, new RestResponse.Crud.View(RestResponse.Crud.STATUS_NOT_FOUND, null));
				return null;
			}
		}
		
		restReturn(result, _format, new RestResponse.Crud.View(RestResponse.STATUS_UNKNOWN_ERROR, null));
		return object;
	}
}
